﻿using System;
using System.Net;
using System.Reflection;
using System.ComponentModel;
using System.Collections.Generic;

namespace net.veekee.common.reflection
{
    public class ReflectionManager
    {
        private static List<Assembly> LoadedAssemblies = null;

        static ReflectionManager()
        {
            ReflectionManager.LoadedAssemblies = new List<Assembly>();
            ReflectionManager.LoadedAssemblies.Add(Assembly.GetExecutingAssembly());
            ReflectionManager.LoadedAssemblies.Add(Assembly.GetEntryAssembly());
            ReflectionManager.LoadedAssemblies.AddRange(AppDomain.CurrentDomain.GetAssemblies());
        }

        public static Assembly AddAssemblyFromFile(String filename)
        {
            try
            {
                Log.GetInstance().Debug("Loading new assembly from file '" + filename + "'");
                Assembly a = Assembly.LoadFile(filename);
                ReflectionManager.LoadedAssemblies.Add(a);
                return a;
            }
            catch (Exception e)
            {
                Log.GetInstance().Error("Call error: " + e.ToString());
            }
            return null;
        }

        private static Assembly GetAssembly(String type)
        {
            Type t = default(Type);
            foreach (Assembly a in ReflectionManager.LoadedAssemblies)
            {
                t = a.GetType(type, false);
                if (t != null)
                    return a;
            }
            return default(Assembly);
        }

        public static Type GetType(String type)
        {
            Type t = default(Type);
            foreach (Assembly a in ReflectionManager.LoadedAssemblies)
            {
                t = a.GetType(type, false);
                if (t != null)
                    return t;
            }
            return default(Type);
        }

        public static Object CreateObject(String type)
        {
            Log.GetInstance().Debug("Creating dynamic object of type '" + type + "'");
            return ReflectionManager.GetAssembly(type).CreateInstance(type);
        }

        public static T CreateObject<T>(String type) where T : class
        {
            Log.GetInstance().Debug("Creating dynamic object of type '" + type + "'");
            return ReflectionManager.GetAssembly(type).CreateInstance(type) as T;
        }

        public static Object CallFunction(String type, String function, params Object[] args)
        {
            return CallFunction<Object>(type, function, args);
        }

        public static Object CallFunction<T>(T instance, String function, params Object[] args)
        {
            return internalCallFunction<Object>(instance.GetType().ToString(), instance, function, args);
        }

        public static R CallFunction<R>(String type, String function, params Object[] args)
        {
            return internalCallFunction<R>(type, null, function, args);
        }

        private static R internalCallFunction<R>(String type, Object instance, String function, params Object[] args)
        {
            try
            {
                Log.GetInstance().Debug("Call static function " + type + "." + function + "()");
                Type thisClass = ReflectionManager.GetType(type);
                if (thisClass == null)
                    return default(R);
                Type[] argTypes = new Type[args.Length];
                for (int i = 0; i < args.Length; i++)
                {
                    argTypes[i] = args[i].GetType();
                }
                MethodInfo m = thisClass.GetMethod(function, argTypes);
                return (R)m.Invoke(instance, args);
            }
            catch (Exception e)
            {
                Log.GetInstance().Error("Call error: " + e.ToString());
            }
            return default(R);
        }

        public static Object CallGetField(String type, String var)
        {
            return CallGetProperty<Object>(type, var);
        }

        public static R CallGetProperty<R>(String type, String var)
        {
            try
            {
                Log.GetInstance().Debug("Call static get field " + type + "." + var + "");
                Type thisClass = ReflectionManager.GetType(type);
                PropertyInfo p = thisClass.GetProperty(var);
                MethodInfo m = p.GetGetMethod();
                return (R)m.Invoke(null, null);
            }
            catch (Exception e)
            {
                Log.GetInstance().Error("Call error: " + e.ToString());
            }
            return default(R);
        }

        public static Type GetFieldType(Object instance, String var)
        {
            try
            {
                String type = instance.GetType().ToString();
                Log.GetInstance().Debug("Retreive field " + type + "." + var + " type");
                Type thisClass = ReflectionManager.GetType(type);
                FieldInfo f = thisClass.GetField(var);
                if (f != null)
                {
                    return f.FieldType;
                }
                else
                {
                    PropertyInfo p = thisClass.GetProperty(var);
                    if (p != null)
                    {
                        return p.PropertyType;
                    }
                }
            }
            catch (Exception e)
            {
                Log.GetInstance().Error("Retreive error: " + e.ToString());
            }
            return typeof(Type);
        }

        public static bool CallSetFieldForce(Object instance, String var, Object val)
        {
            try
            {
                String type = instance.GetType().ToString();
                Log.GetInstance().Debug("Call get field " + type + "." + var + " (force mode)");
                Type thisClass = ReflectionManager.GetType(type);
                FieldInfo f = thisClass.GetField(var);
                if (f != null)
                {
                    MethodInfo m = f.FieldType.GetMethod("Parse", new Type[] { val.GetType() });
                    if (m != null)
                    {
                        Object result = m.Invoke(null, new Object[] { val });
                        f.SetValue(instance, result);
                    }
                    else
                    {
                        f.SetValue(instance, val);
                    }
                    return true;
                }
                else
                {
                    PropertyInfo p = thisClass.GetProperty(var);
                    if (p != null)
                    {
                        MethodInfo m = p.GetSetMethod(true);
                        if (m != null)
                        {
                            p.SetValue(instance, val, null);
                            return true;
                        }
                    }
                }
            }
            catch (Exception e)
            {
                Log.GetInstance().Error("Call error: " + e.ToString());
            }
            return false;
        }

        public static void CallSetField<T, R>(T instance, String var, R val)
        {
            try
            {
                String type = typeof(T).ToString();
                Log.GetInstance().Debug("Call get field " + type + "." + var + "");
                Type thisClass = ReflectionManager.GetType(type);
                FieldInfo f = thisClass.GetField(var);
                if (f != null)
                    f.SetValue(instance, val);
            }
            catch (Exception e)
            {
                Log.GetInstance().Error("Call error: " + e.ToString());
            }
        }

        public static String CallGetFieldAttribute(String attribute, Enum en)
        {
            try
            {
                Log.GetInstance().Debug("Call get field " + attribute + " attribute for " + en.ToString());
                FieldInfo fieldInfo = en.GetType().GetField(en.ToString());
                if (attribute.ToLower() == "description")
                {
                    DescriptionAttribute[] enumAttributes = (DescriptionAttribute[])fieldInfo.GetCustomAttributes(typeof(DescriptionAttribute), false);
                    if (enumAttributes.Length > 0)
                    {
                        return enumAttributes[0].Description;
                    }
                }
                else if (attribute.ToLower() == "category")
                {
                    CategoryAttribute[] enumAttributes = (CategoryAttribute[])fieldInfo.GetCustomAttributes(typeof(CategoryAttribute), false);
                    if (enumAttributes.Length > 0)
                    {
                        return enumAttributes[0].Category;
                    }
                }
            }
            catch (Exception e)
            {
                Log.GetInstance().Error("Call error: " + e.ToString());
            }
            return en.ToString();
        }

        public static A GetClassAttribute<T, A>() where A : Attribute
        {
            Object[] attrList = typeof(T).GetCustomAttributes(true);
            foreach (Object attr in attrList)
            {
                A a = attr as A;
                if (a == null)
                    continue;
                return a;
            }
            return default(A);
        }

        public static IEnumerable<KeyValuePair<PropertyInfo, A>> GetPropertyAttributes<T, A>() where A : Attribute
        {
            PropertyInfo[] pList = typeof(T).GetProperties();
            foreach (PropertyInfo p in pList)
            {
                try
                {
                    Object[] attrList = p.GetCustomAttributes(true);
                    foreach (Object attr in attrList)
                    {
                        A a = attr as A;
                        if (a == null)
                            continue;
                        yield return new KeyValuePair<PropertyInfo, A>(p, a);
                    }
                }
                finally
                {
                }
            }
        }
    }
}
